/*
 * sysmo 服务器已连接监控项信息
 * Copyright (C) 2009  YuLicheng
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sysmo.server.node.item;

import java.util.List;
import java.util.Vector;
import org.sysmo.communication.CommunicationInterface;
import org.sysmo.communication.CommunicationProtocol;
import org.sysmo.db.DatabaseInterface;
import org.sysmo.db.DbItem;
import org.sysmo.server.node.item.graphgroup.GraphGroup;
import org.sysmo.server.util.ChildEventListener;
import org.sysmo.server.util.InstanceNotInitializedException;
import org.w3c.dom.NodeList;

/**
 * 
 * 已连接客户端的监控项。这类监控项能够查看历史图表以及对某其进行远程配置。
 *
 * @author YuLicheng
 */
public class ConnectedItem extends Item {

    private CommunicationInterface client;
    private ItemScript monitorScript = null;

    /**
     * 事件处理对象。
     */
    public ItemEvent event = new ItemEvent();

    /**
     * 实例化数据库中已存在的监控项。构造时会立即从客户端读取部分配置信息。
     *
     * @param eventListener 子对象监听接口。
     * @param dbInterface 数据库访问接口。
     * @param client 客户端通讯连接。
     * @param id 已存在的监控项ID。
     * @throws ItemNotExistsException ID不存在。
     */
    public ConnectedItem(ChildEventListener<Item> eventListener,
            DatabaseInterface dbInterface, CommunicationInterface client,
            int id) throws ItemNotExistsException {
        super(eventListener, dbInterface, id);
        this.client = client;
        monitorScript = new ItemScript(client, item.iid, event);
        monitorScript.getSettingsFromClient();
    }

    /**
     * 创建一个新的监控项。新监控项创建后，会发送消息到所属客户端，通知其新建监控项
     * ID。此时客户端不会执行任何采集数据的操作，效果就如同调用了
     * {@link #pauseDataSending()}方法。因此在设置完成需要手动调用
     * {@link #resumeDataSending()}方法来使得客户端开始数据采集和发送。
     *
     * @param eventListener 子对象监听接口。
     * @param dbInterface 数据库访问接口。
     * @param client 客户端通讯连接。
     * @param name 新监控项名称。
     * @param nodeID 所属节点的ID。
     * @throws ItemExistsException 监控项名已存在。
     */
    public ConnectedItem(ChildEventListener<Item> eventListener,
            DatabaseInterface dbInterface, CommunicationInterface client,
            String name, int nodeID)
            throws ItemExistsException {
        super(eventListener, dbInterface);
        try {
            if (dbInterface.query_ItemTable(name, nodeID) != null) {
                throw new ItemExistsException(name);
            }
            item = new DbItem();
            item.cid = nodeID;
            //TODO: 为DbItem指定新建对象ID
            item.iid = 0;
            item.name = name;
            item.mail = "";
            item.time_interval = 300;
            dbInterface.update_ItemTable(item);
            client.sendString(CommunicationProtocol.encapsulateWithItemID(
                    5, item.iid, ""));
        } catch (Exception e) {
            e.printStackTrace();
        }
        monitorScript = new ItemScript(client, item.iid, event);
    }

    /**
     * 将此监控项的配置重新部署到客户端。此方法会给客户端连续发送类型为5的消息在客户
     * 端重新添加当前的监控项，但客户端将没有其他数据，此监控项无法进行数据采集。因此
     * 此方法还会产生一条警告信息提示管理员。
     * 
     * @param nodeID 与之同步的节点ID，用于记录警告信息。
     * @param nodeName 与之同步的节点名称，用于记录警告信息。
     */
    public void resyncWithClient(int nodeID, String nodeName) {
        try {
            client.sendString(CommunicationProtocol.encapsulateWithItemID(
                    5, item.iid, ""));
        } catch (Exception e) {
            e.printStackTrace();
        }
        //TODO: 确定警报项是否可以在没有数据记录的情况下添加。
        Warning warning = new Warning(dbInterface, "客户端节点“" + nodeName +
                "”缺少监控项：" + getName());
        warning.setNodeID(nodeID);
        warning.setItemID(item.iid);
        //TODO: 确定无效GraphID的值。
        warning.setGraphID(0);
        //TODO: 确定无效ValueID的值。
        warning.setValueID(0);
        //TODO: 确定无效ResultID的值。
        warning.setResultID(0);
        warning.saveData();
    }

    /**
     * 此对象即将被删除时自动调用此方法。此方法给客户端发送监控被删除的消息。
     */
    @Override
    protected void readyToRemove() {
        try {
            client.sendString(CommunicationProtocol.encapsulateWithItemID(
                    13, item.iid, ""));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置监控项名称。
     *
     * @param name 此监控项的新名称。
     * @throws ItemExistsException 监控项名已存。
     */
    public void setName(String name) throws ItemExistsException {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        item.name = name;
    }

    /**
     * 设置出现警告时要将其转发的邮件地址等联系方式。
     *
     * @param contect 联系方式。
     */
    public void setWarningContect(WarningContect contect) {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        item.mail = contect.getEmail();
    }

    /**
     * 新建或获取数据获取脚本。若没有相应的数据则返回一个新的空脚本对象。得到此对象
     * 后，可使用此对象提供的方法对脚本进行操作，如设置或发送到客户端等。
     * <br />
     *
     * 由于服务器上没有保存数据获取脚本，因此每次需要得到此脚本时都要从客户端获取。
     *
     * @return 数据获取脚本。
     */
    public ItemScript getMonitorScript() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        return monitorScript;
    }

    /**
     * 设置数据验证脚本。此脚本为JavaScript。
     * <br />
     *
     * 数据验证脚本设置后将会被立即发送到客户端，服务器上并不会保存此脚本的任何内容。
     * 
     * @param javaScript 新的数据验证脚本。
     * @see #pauseDataSending()
     */
    public void setValidateScript(String javaScript) {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        try {
            client.sendString(
                    CommunicationProtocol.encapsulateWithItemID(8, item.iid,
                    "<script><![CDATA[" + javaScript + "]]></script>"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回数据验证脚本。
     * <br />
     *
     * 由于服务器上没有保存数据验证脚本，因此每次需要得到此脚本时都要从客户端获取。
     *
     * @return 数据验证脚本。
     */
    public String getValidateScript() {
        String script = "";
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        try {
            client.sendString(CommunicationProtocol.encapsulateWithItemID(
                    10, item.iid, ""));
            event.waitEvent(12);
            NodeList list = event.messageDocument.getElementsByTagName("script");
            if (list.getLength() > 0) {
                script = list.item(0).getFirstChild().getNodeValue();
            }
            event.exitEvent();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return script;
    }

    /**
     * 设置数据采集时间间隔。此值以分钟为单位。可设置的范围为1分钟到1天。调用此方法
     * 后，设置的新时间间隔会被立即发送的客户端。
     * <br />
     *
     * 注意：修改数据采集时间间隔会导致图表的某些计算（如积分、求导）失败。因此这些
     * 图表中的监控值的历史记录可能会被完全删除。
     *
     * @param interval 新的时间间隔。
     * @throws IllegalArgumentException 若新间隔不在1分钟到1天内则抛出此异常。
     * @see #pauseDataSending()
     */
    public void setRunInterval(int interval) throws IllegalArgumentException {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        item.time_interval = interval;
        try {
            client.sendString(CommunicationProtocol.encapsulateWithItemID(6,
                    item.iid, "<timeInterval>" + interval + "</timeInterval>"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送消息给客户端要求其暂停数据采集和发送。在对参数进行设置时，应该先调用此方
     * 法，以保证客户端不会在参数没有完全设置完成前发送不一致的监控记录信息。
     * <br />
     *
     * 注意：此方法只会使客户端暂停当前监控项的数据发送，而不会影响其他监控项。而在调
     * 用此方法后到调用resumeDataSending方法前产生的所有数据记录都丢弃。（若客户端在
     * 此期间重新启动，则可能自行恢复数据采集和发送。）
     *
     * @see #resumeDataSending()
     */
    public void pauseDataSending() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        try {
            client.sendString(
                    CommunicationProtocol.encapsulateWithItemID(15, item.iid, ""));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送消息给客户端要求其开始数据采集和发送。在调用{@link #pauseDataSending()}
     * 方法后调用此方法可恢复客户端运行。
     *
     * @see #pauseDataSending()
     */
    public void resumeDataSending() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        try {
            client.sendString(
                    CommunicationProtocol.encapsulateWithItemID(16, item.iid, ""));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 保存客户端发送的监控数据。此方法会将相应的数据存入数据库。此方法还会处理客户端
     * 发出的警报。
     * <br />
     *
     * 此方法会调用相应的监控图表的saveData方法来存储。
     *
     * @param results 监控结果集合。
     */
    public void saveData(List<ReceivedResult> results) {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        Vector<ReceivedResult> thisResults = new Vector<ReceivedResult>();
        for (ReceivedResult res : results) {
            //从Node处传递的ReceivedResult列表内总是此监控项的信息。
            Warning warning = res.getWarning();
            if (warning != null) {
                warning.setItemID(item.iid);
            }
            thisResults.add(res);
        }
        for (GraphGroup graph : allGraphs.values()) {
            graph.saveData(thisResults);
        }
    }

}
